/**
 * 
 */
package inz.model.classification;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/** Class representing borders of sets (e.g. JEP spaces) */
public class Border
{
	private Set<Set<Integer>> leftBorder = new HashSet<Set<Integer>>();
	private Set<Set<Integer>> rightBorder = new HashSet<Set<Integer>>();

	public Border()
	{
	}

	/** @SuppressWarnings can make only shallow copy */
	public Border(Border border)
	{
		for (Set<Integer> set : border.getLeft())
		{
			leftBorder.add(new HashSet<Integer>(set));
		}
		for (Set<Integer> set : border.getRight())
		{
			rightBorder.add(new HashSet<Integer>(set));
		}
	}

	public void addExample(List<Integer> exampleSet)
	{
		Set<Integer> set = new HashSet<Integer>();
		set.addAll(exampleSet);
		addSet(set, rightBorder);
	}

	public void addExample(Set<Integer> exampleSet)
	{
		addSet(exampleSet, rightBorder);
	}

	// TODO
	private void addRightAll(Set<Set<Integer>> right)
	{
		for (Set<Integer> set : right)
		{
			addSet(set, rightBorder);
		}
	}

	// TODO
	private void addLeftAll(Set<Set<Integer>> left)
	{
		leftBorder.addAll(left);
		removeNotMinimal(leftBorder);
	}

	// TODO
	private void addRight(Set<Integer> right)
	{
		addSet(right, rightBorder);
	}

	// TODO
	private void addLeft(Set<Integer> left)
	{
		leftBorder.add(left);
		removeNotMinimal(leftBorder);
	}

	private void addLeft(List<Integer> left)
	{
		Set<Integer> set = new HashSet<Integer>();
		set.addAll(left);
		leftBorder.add(set);
		removeNotMinimal(leftBorder);
	}

	/** Union of two borders
	 * @param border1
	 * @param border2
	 * @return border1 u border2 (border1 + border2) */
	public static Border union(Border border1, Border border2)
	{
		Border resBorder = new Border();
		resBorder.addRightAll(border1.getRight());
		resBorder.addRightAll(border2.getRight());
		resBorder.addLeftAll(border1.leftBorder);
		resBorder.addLeftAll(border2.leftBorder);
		return resBorder;
	}

	/** Union with another border
	 * @param border border to add
	 * @return thisBorder u border (thisBorder + border2) */
	public void union(Border border)
	{
		addRightAll(border.getRight());
		addLeftAll(border.getLeft());
	}

	/** Intersection of two borders
	 * @param border1
	 * @param border2
	 * @return border1 n border2 (border1 and border2) */
	public static Border intersect(Border border1, Border border2)
	{
		Border resBorder = new Border();

		// right bound
		for (Set<Integer> borderSet1 : border1.getRight())
		{
			for (Set<Integer> borderSet2 : border2.getRight())
			{
				resBorder.addRight(intersect(borderSet1, borderSet2));
			}
		}

		// left bound
		for (Set<Integer> borderSet1 : border1.getLeft())
		{
			for (Set<Integer> borderSet2 : border2.getLeft())
			{
				Set<Integer> newSet = new HashSet<Integer>(borderSet1);
				newSet.addAll(borderSet2);
				resBorder.addLeft(newSet);
			}
		}
		minimizeLeft(resBorder);

		return resBorder;
	}

	/** Intersection with another border
	 * @param border1 border to intersect with */
	public void intersect(Border border2)
	{
		// right bound
		for (Set<Integer> borderSet1 : rightBorder)
		{
			for (Set<Integer> borderSet2 : border2.getRight())
			{
				addSet(intersect(borderSet1, borderSet2), rightBorder);
			}
		}
		
		// left bound
		for (Set<Integer> borderSet1 : leftBorder)
		{
			for (Set<Integer> borderSet2 : border2.getLeft())
			{
				Set<Integer> newSet = new HashSet<Integer>(borderSet1);
				newSet.addAll(borderSet2);
				leftBorder.add(newSet);
				removeNotMinimal(leftBorder);
			}
		}
		minimizeLeft(this);
	}

	public static Set<Integer> intersect(Set<Integer> set1, Set<Integer> set2)
	{
		Set<Integer> resultSet = new HashSet<Integer>(set1);
		resultSet.retainAll(set2);
		return resultSet;
	}

	/** Removes left subsets that are not contained by any of right subset
	 * @param border */
	public static void minimizeLeft(Border border)
	{
		Iterator<Set<Integer>> borderIter = border.getLeft().iterator();
		boolean wasEmpty = border.getLeft().isEmpty();
		while (borderIter.hasNext())
		{
			Set<Integer> thisSet = borderIter.next();
			boolean contained = false;
			for (Set<Integer> borderSet : border.getRight())
			{
				if (borderSet.containsAll(thisSet))
				{
					contained = true;
					break;
				}
			}
			if (!contained)
			{
				borderIter.remove();
			}
		}
		if (!wasEmpty)
		{
			if (border.getLeft().isEmpty())
			{
				border.getRight().clear();
			}
		}
	}

	/** adds another example in set of existing attributes representation */
	public void addSet(Set<Integer> exampleSet, Set<Set<Integer>> destinationSet)
	{
		if (exampleSet == null || exampleSet.isEmpty())
		{
			return;
		}
		// don't insert if is subset of existing
		for (Set<Integer> borderSet : destinationSet)
		{
			if (borderSet.containsAll(exampleSet))
			{
				return;
			}
		}
		// remove existing subsets
		Iterator<Set<Integer>> borderIter = destinationSet.iterator();
		while (borderIter.hasNext())
		{
			if (exampleSet.containsAll(borderIter.next()))
			{
				borderIter.remove();
			}
		}
		HashSet<Integer> set = new HashSet<Integer>();
		set.addAll(exampleSet);
		destinationSet.add(set);
	}

	/** Performs difference operation on two horizontal spaces
	 * @param border1 first border
	 * @param border2 second border
	 * @return border1 - border2 */
	public static Border JepProduce(Border border1, Border border2)
	{
		Border resBorder = new Border();
		Border tmpBorder;
		for (Set<Integer> borderSet1 : border1.getRight())
		{
			boolean skip = false;
			for (Set<Integer> borderSet2 : border2.getRight())
			{
				if (borderSet2.containsAll(borderSet1))
				{
					skip = true;
					break;
				}
			}
			if (!skip)
			{
				tmpBorder = subDiff(borderSet1, border2);
				resBorder.addRightAll(tmpBorder.getRight());
				resBorder.addLeftAll(tmpBorder.getLeft());
			}
		}
		return resBorder;
	}

	/** Computes difference operation for two horizontal spaces ( borders with empty left borders)
	 * @param borderSet
	 * @param border2
	 * @return Border border */
	public static Border subDiff(Set<Integer> borderSet, Border border2)
	{
		Border resBorder = new Border();
		Set<Set<Integer>> tmpSet = new HashSet<Set<Integer>>();
		tmpSet.add(borderSet);
		resBorder.addRightAll(tmpSet);

		Set<Set<Integer>> leftSet = new HashSet<Set<Integer>>();
		int k = 1;
		for (Set<Integer> borderSet2 : border2.getRight())
		{
			if (k == 1)
			{
				for (int elem : diff(borderSet, borderSet2))
				{
					Set<Integer> elemSet = new HashSet<Integer>();
					elemSet.add(elem);
					leftSet.add(elemSet);
				}
			}
			else
			{
				Iterator<Set<Integer>> setIter = leftSet.iterator();
				Set<Set<Integer>> newSets = new HashSet<Set<Integer>>();
				while (setIter.hasNext())
				{
					Set<Integer> existingSet = setIter.next();
					for (int elem : diff(borderSet, borderSet2))
					{
						Set<Integer> newSet = new HashSet<Integer>(existingSet);
						newSet.add(elem);
						newSets.add(newSet);
					}
					setIter.remove();
				}
				leftSet.addAll(newSets);
				removeNotMinimal(leftSet);
			}
			k++;
		}
		resBorder.addLeftAll(leftSet);
		return resBorder;
	}
	
	/** Removes elements that are not minimal - that contain other elements from set
	 * @param set set to filter */
	private static void removeNotMinimal(Set<Set<Integer>> set)
	{
		Iterator<Set<Integer>> setIter = set.iterator();
		while (setIter.hasNext())
		{
			Set<Integer> thisSet = setIter.next();
			boolean minimal = true;
			for (Set<Integer> otherSet : set)
			{
				if (!thisSet.equals(otherSet) && thisSet.containsAll(otherSet))
				{
					minimal = false;
					break;
				}
			}
			
			if (!minimal)
			{
				setIter.remove();
			}
		}
	}


	/** Creates set from elements from set1 without elements existing in set2
	 * @param set1
	 * @param set2
	 * @return set1 - set2 */
	public static Set<Integer> diff(Set<Integer> set1, Set<Integer> set2)
	{
		Set<Integer> copy = new HashSet<Integer>(set1);
		copy.removeAll(set2);
		return copy;
	}

	public Set<Set<Integer>> getLeft()
	{
		return leftBorder;
	}

	public Set<Set<Integer>> getRight()
	{
		return rightBorder;
	}

	public void print()
	{
		System.out.println("<" + leftBorder + " - " + rightBorder + ">");
	}

	public static void main(String[] args)
	{
		System.out.println("------------BORDER TEST-------------");
		Border b1 = new Border();
		b1.addExample(Arrays.asList(1, 3));
		b1.addExample(Arrays.asList(1, 2));
		b1.addExample(Arrays.asList(3, 4));
		b1.addExample(Arrays.asList(2));
		b1.addExample(Arrays.asList(1, 3, 4));
		b1.addExample(Arrays.asList(3));
		b1.print();
		Border b2 = new Border();
		b2.addExample(Arrays.asList(1, 2));
		// b2.addExample(Arrays.asList(1, 3));
		b2.addExample(Arrays.asList(1, 2, 3));
		b2.addExample(Arrays.asList(3, 4));
		b2.print();

		Border b3 = new Border();
		b3.addLeft(Arrays.asList(1));
		b3.addLeft(Arrays.asList(3, 4));
		b3.addExample(Arrays.asList(1, 3));
		b3.addExample(Arrays.asList(2, 3, 4));
		b3.print();

		Border b4 = new Border();
		b4.addLeft(Arrays.asList(2));
		b4.addExample(Arrays.asList(1, 2, 3));
		b4.print();

		System.out.println("-------test diff-----");
		Set<Integer> set1 = new HashSet<Integer>();
		set1.addAll(Arrays.asList(1, 2, 3, 4));
		Set<Integer> set2 = new HashSet<Integer>();
		set2.addAll(Arrays.asList(1, 2));

		System.out.println(diff(set1, set2));

		System.out.println("-------test sub diff-----");
		subDiff(set1, b2).print();
		JepProduce(subDiff(set1, b2), b2).print();
		System.out.println("-------test JEP produce-----");
		JepProduce(b1, b2).print();
		System.out.println("-------test Union-----");
		union(b3, b4).print();
		System.out.println("-------test Intersection-----");
		intersect(b3, b4).print();
		System.out.println("------------DONE-------------");

		// Border testBorderSameAs1 = new Border(b3);
	}

	@Override
	public String toString()
	{
		return "<" + leftBorder + " - " + rightBorder + ">";
	}

}
